package repostiory

import (
	"context"
	"database/sql"
	"gitee.com/chicken-c/go-learn/webook/internal/domain"
	"gitee.com/chicken-c/go-learn/webook/internal/repostiory/cache"
	"gitee.com/chicken-c/go-learn/webook/internal/repostiory/dao"
	"github.com/gin-gonic/gin"
	"time"
)

var (
	ErrUserDuplicateEmail = dao.ErrUserDuplicateEmail
	ErrUserNotFound       = dao.ErrRecordNotFound
)

type UserRepository interface {
	Create(ctx context.Context, u domain.User) error
	FindByEmail(ctx context.Context, email string) (domain.User, error)
	FindByPhone(ctx *gin.Context, phone string) (domain.User, error)
	Modify(ctx context.Context, u domain.User) error
	FindById(ctx context.Context, userId int64) (domain.User, error)
	FindByWechat(ctx *gin.Context, openId string) (domain.User, error)
}

type CachedUserRepository struct {
	dao   dao.UserDAO
	cache cache.UserCache
}

func NewCachedUserRepository(dao dao.UserDAO, cache cache.UserCache) UserRepository {
	return &CachedUserRepository{
		dao:   dao,
		cache: cache,
	}
}

func (repo *CachedUserRepository) Create(ctx context.Context, u domain.User) error {
	return repo.dao.Insert(ctx, repo.domainToEntity(u))
}

func (repo *CachedUserRepository) FindByEmail(ctx context.Context, email string) (domain.User, error) {
	ue, err := repo.dao.FindByEmail(ctx, email)
	if err == dao.ErrRecordNotFound {
		return domain.User{}, ErrUserNotFound
	}
	if err != nil {
		return domain.User{}, err
	}

	return repo.entityToDomain(ue), err
}

func (repo *CachedUserRepository) FindByPhone(ctx *gin.Context, phone string) (domain.User, error) {
	ue, err := repo.dao.FindByPhone(ctx, phone)
	if err == dao.ErrRecordNotFound {
		return domain.User{}, ErrUserNotFound
	}
	return repo.entityToDomain(ue), err
}

func (repo *CachedUserRepository) Modify(ctx context.Context, u domain.User) error {
	ue := repo.domainToEntity(u)
	return repo.dao.Update(ctx, ue)
}

func (repo *CachedUserRepository) FindById(ctx context.Context, userId int64) (domain.User, error) {
	//先找cache
	//cache没有就去找db
	//回种到cache
	u, err := repo.cache.Get(ctx, userId)
	if err == nil {
		//cache存在数据
		return u, err
	}

	//数据不存在和cache崩溃的情况
	//cache miss了我们可以去db查询，但是cache崩溃了的情况我们需要考虑雪崩，穿透的情况
	//这也是我们要在cache层返回特殊error的目的,但是在这里我们直接去查DB.
	ue, err := repo.dao.FindById(ctx, userId)
	if err == dao.ErrRecordNotFound {
		//cache 没有 db也没有
		//缓存击穿问题
		return domain.User{}, ErrUserNotFound
	}
	if err != nil {
		return domain.User{}, err
	}

	user := repo.entityToDomain(ue)
	err = repo.cache.Set(ctx, user)
	if err != nil {
		//查cache失败，写入也失败的情况，
		//记录下日志，做监控
	}
	return user, err
}

func (repo *CachedUserRepository) FindByWechat(ctx *gin.Context, openId string) (domain.User, error) {
	u, err := repo.dao.FindByWechat(ctx, openId)
	if err == dao.ErrRecordNotFound {
		return domain.User{}, ErrUserNotFound
	}
	return repo.entityToDomain(u), err
}

func (repo *CachedUserRepository) entityToDomain(ue dao.User) domain.User {
	var birthday time.Time
	if ue.Birthday.Valid {
		birthday = time.UnixMilli(ue.Birthday.Int64)
	}
	return domain.User{
		Id:       ue.Id,
		Email:    ue.Email,
		Password: ue.Password,
		Phone:    ue.Phone.String,
		WechatInfo: domain.WechatInfo{
			OpenId:  ue.WechatOpenId.String,
			UnionId: ue.WechatUnionId.String,
		},
		Nickname: ue.Nickname.String,
		AboutMe:  ue.AboutMe.String,
		Birthday: birthday,
	}
}

func (repo *CachedUserRepository) domainToEntity(ue domain.User) dao.User {
	birthday := ue.Birthday.UnixMilli()

	return dao.User{
		Id:       ue.Id,
		Email:    ue.Email,
		Password: ue.Password,
		Phone: sql.NullString{
			String: ue.Phone,
			Valid:  ue.Phone != "",
		},
		WechatOpenId: sql.NullString{
			String: ue.WechatInfo.OpenId,
			Valid:  ue.WechatInfo.OpenId != "",
		},
		WechatUnionId: sql.NullString{
			String: ue.WechatInfo.UnionId,
			Valid:  ue.WechatInfo.UnionId != "",
		},
		Nickname: sql.NullString{
			String: ue.Nickname,
			Valid:  ue.Nickname != "",
		},
		AboutMe: sql.NullString{
			String: ue.AboutMe,
			Valid:  ue.AboutMe != "",
		},
		Birthday: sql.NullInt64{
			Int64: birthday,
			Valid: !ue.Birthday.IsZero(),
		},
	}
}
